package info.u250.c2d.box2deditor.gdx.scenes;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.QueryCallback;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.NinePatchDrawable;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Scaling;
import info.u250.c2d.box2d.Box2dObject;
import info.u250.c2d.box2d.model.b2BodyDefModel;
import info.u250.c2d.box2d.model.b2FixtureDefModel;
import info.u250.c2d.box2d.model.b2JointDefModel;
import info.u250.c2d.box2d.model.fixture.b2CircleFixtureDefModel;
import info.u250.c2d.box2d.model.fixture.b2RectangleFixtureDefModel;
import info.u250.c2d.box2deditor.adapter.*;
import info.u250.c2d.box2deditor.gdx.PhysicalWorld;
import info.u250.c2d.box2deditor.gdx.support.AbstractBox2dHelper;
import info.u250.c2d.box2deditor.gdx.support.DefaultLabel;
import info.u250.c2d.box2deditor.gdx.support.Geometry;
import info.u250.c2d.box2deditor.gdx.support.LeftTopImage;
import info.u250.c2d.engine.Engine;

public class SceneHelper extends AbstractBox2dHelper<SceneModelAdapter> {
    private b2BodyDefModel data = null;
    private b2BodyDefModel secondData = null;
    private Vector2 dragTemp = new Vector2();
    private Vector2 startPoint = new Vector2();
    final Vector2 box2dAABBTestPoint = new Vector2();
    InputMultiplexer input = new InputMultiplexer();
    LeftTopImage bodyMode, jointModel;
    private boolean rotateOn = false;

    private Color colorInner = new Color(1f, 1, 1f, 0.2f);
    private Color colorOuter = new Color(0, 0, 0, 0.2f);

    QueryCallback callback = new QueryCallback() {
        @Override
        public boolean reportFixture(Fixture fixture) {
            // if the hit point is inside the fixture of the body
            // we report it
            if (fixture.testPoint(box2dAABBTestPoint.x, box2dAABBTestPoint.y)) {
                Body hitBody = fixture.getBody();
                for (b2BodyDefModel bodyModel : PhysicalWorld.MODEL.bodyDefModels) {
                    if (hitBody == bodyModel.body) {
                        data = bodyModel;
                        adapter.callUI.updateToUI(bodyModel);
                        return false;
                    }
                }
                data = null;
                secondData = null;
                return false;
            } else {
                secondData = null;
                return true;
            }
        }
    };
    InputAdapter jointSelectInput = new InputAdapter() {
        MakeJointsMenu jointsMenu = new MakeJointsMenu();
        QueryCallback callbackSecond = new QueryCallback() {
            @Override
            public boolean reportFixture(Fixture fixture) {
                if (fixture == null) return true;
                // if the hit point is inside the fixture of the body
                // we report it
                if (fixture.testPoint(box2dAABBTestPoint.x, box2dAABBTestPoint.y)) {
                    Body hitBody = fixture.getBody();
                    for (b2BodyDefModel bodyModel : PhysicalWorld.MODEL.bodyDefModels) {
                        if (hitBody == bodyModel.body) {
                            //not the same body
                            if (data == bodyModel) return true;

                            secondData = bodyModel;
                            adapter.callUI.updateToUI(secondData);
                            if (!Gdx.input.isButtonPressed(Buttons.LEFT)) {
                                jointsMenu.setScale(Engine.getDefaultCamera().zoom);
                                jointsMenu.setPosition(worldCenter.x - 100, worldCenter.y - 100);
                                addActor(jointsMenu);
                            }
                            return false;
                        }
                    }
                    secondData = null;
                    return false;
                } else {
                    return true;
                }
            }
        };

        public boolean touchDown(int screenX, int screenY, int pointer, int button) {
            if (button == Buttons.LEFT) {
                jointsMenu.remove();
                dragTemp.set(Engine.screenToWorld(screenX, screenY));
                startPoint.set(dragTemp);
                data = null;
                secondData = null;
                //find the first data //
                if (button == Buttons.LEFT) {
                    box2dAABBTestPoint.set(dragTemp).scl(1f / Box2dObject.RADIO);
                    PhysicalWorld.WORLD.QueryAABB(callback, box2dAABBTestPoint.x - 0.1f, box2dAABBTestPoint.y - 0.1f, box2dAABBTestPoint.x + 0.1f, box2dAABBTestPoint.y + 0.1f);
                }
            }
            return super.touchDown(screenX, screenY, pointer, button);
        }

        public boolean touchDragged(int screenX, int screenY, int pointer) {
            secondData = null;
            dragTemp.set(Engine.screenToWorld(screenX, screenY));
            //find the first data //
            box2dAABBTestPoint.set(dragTemp).scl(1f / Box2dObject.RADIO);
            PhysicalWorld.WORLD.QueryAABB(callbackSecond, box2dAABBTestPoint.x - 0.1f, box2dAABBTestPoint.y - 0.1f, box2dAABBTestPoint.x + 0.1f, box2dAABBTestPoint.y + 0.1f);
            return super.touchDragged(screenX, screenY, pointer);
        }

        ;

        @Override
        public boolean touchUp(int x, int y, int pointer, int button) {
            if (button == Buttons.LEFT) {
                secondData = null;
                dragTemp.set(Engine.screenToWorld(x, y));
                //find the first data //
                box2dAABBTestPoint.set(dragTemp).scl(1f / Box2dObject.RADIO);
                PhysicalWorld.WORLD.QueryAABB(callbackSecond, box2dAABBTestPoint.x - 0.1f, box2dAABBTestPoint.y - 0.1f, box2dAABBTestPoint.x + 0.1f, box2dAABBTestPoint.y + 0.1f);
            }
            return false;
        }

        public boolean keyDown(int keycode) {
            if (keycode == Keys.SPACE) {
                jointsMenu.remove();
                data = null;
                secondData = null;
                input.clear();
                input.addProcessor(SceneHelper.this);
                input.addProcessor(bodySelectInput);
                jointModel.remove();
                addActor(bodyMode);
            } else if (keycode == Keys.F5) {
                jointModel.remove();
                adapter.simulation();
            }
            return super.keyDown(keycode);
        }
    };
    InputAdapter bodySelectInput = new InputAdapter() {
        @Override
        public boolean touchDown(int screenX, int screenY, int pointer, int button) {
            dragTemp.set(Engine.screenToWorld(screenX, screenY));
            secondData = null;
            float dst = 0;
            if (data != null) {
                dst = data.position.dst(dragTemp);
            }
            if (dst > 100 && dst < 150) {
                rotateOn = true;
            } else {
                data = null;
                //find the first data //
                if (button == Buttons.LEFT) {
                    box2dAABBTestPoint.set(dragTemp).scl(1f / Box2dObject.RADIO);
                    PhysicalWorld.WORLD.QueryAABB(callback, box2dAABBTestPoint.x - 0.1f, box2dAABBTestPoint.y - 0.1f, box2dAABBTestPoint.x + 0.1f, box2dAABBTestPoint.y + 0.1f);
                }
            }
            return super.touchDown(screenX, screenY, pointer, button);
        }

        @Override
        public boolean touchDragged(int x, int y, int pointer) {
            if (null != data) {
                Vector2 current = Engine.screenToWorld(x, y);
                if (rotateOn) {
                    float angle_append = current.cpy().sub(data.position).angle() - dragTemp.sub(data.position).angle();
                    data.degrees += angle_append;
                    adapter.callUI.updateToUI(data);
                } else {
                    secondData = null;
                    Vector2 offset = current.cpy().sub(dragTemp);
                    data.position.add(offset.x, offset.y);
                    adapter.callUI.updateToUI(data);
                }
                dragTemp.set(current);

            }
            return super.touchDragged(x, y, pointer);
        }

        public boolean touchUp(int screenX, int screenY, int pointer, int button) {
            rotateOn = false;
            return false;
        }

        ;

        public boolean keyDown(int keycode) {
            if (keycode == Keys.SPACE) {
                data = null;
                secondData = null;
                input.clear();
                input.addProcessor(SceneHelper.this);
                input.addProcessor(jointSelectInput);
                bodyMode.remove();
                addActor(jointModel);
            } else if (keycode == Keys.F5) {
                jointModel.remove();
                adapter.simulation();
            }
            return super.keyDown(keycode);
        }
    };


    public SceneHelper(final MainScene adapter) {
        super(adapter);
        TextureAtlas atlas = Engine.resource("TA");
        bodyMode = new LeftTopImage(atlas.findRegion("bodyMode"));
        jointModel = new LeftTopImage(atlas.findRegion("jointMode"));
        this.addActor(bodyMode);
    }

    @Override
    public void render(float delta) {
        boolean bodySelectMode = input.getProcessors().contains(bodySelectInput, true);
        Gdx.gl.glEnable(GL20.GL_BLEND);
        if (bodySelectMode && null != data) {
            render.setColor(colorOuter);
            render.begin(ShapeType.Filled);
            render.circle(data.position.x, data.position.y, 150);
            render.end();
            render.setColor(colorInner);
            render.begin(ShapeType.Filled);
            render.circle(data.position.x, data.position.y, 100);
            render.end();
            render.setColor(colorOuter);
            render.begin(ShapeType.Filled);
            render.triangle(
                    data.position.x + 100 * MathUtils.cosDeg(data.degrees - 30),
                    data.position.y + 100 * MathUtils.sinDeg(data.degrees - 30),
                    data.position.x + 100 * MathUtils.cosDeg(data.degrees + 90),
                    data.position.y + 100 * MathUtils.sinDeg(data.degrees + 90),
                    data.position.x + 100 * MathUtils.cosDeg(data.degrees + 210),
                    data.position.y + 100 * MathUtils.sinDeg(data.degrees + 210));
            render.end();
        }
        try {
            for (b2BodyDefModel b2 : model.bodyDefModels) {
                final Vector2 position = b2.position;
                final float angle = b2.degrees;

                for (b2FixtureDefModel obj : b2.fixtures) {
                    if (obj instanceof PolygonFixtureDefModel) {
                        Geometry.renderPolygon(PolygonFixtureDefModel.class.cast(obj), position, angle, !bodySelectMode && b2 == data || b2 == secondData);
                    } else if (obj instanceof b2CircleFixtureDefModel) {
                        Geometry.renderCircle(b2CircleFixtureDefModel.class.cast(obj), position, angle, !bodySelectMode && b2 == data || b2 == secondData);
                    } else if (obj instanceof b2RectangleFixtureDefModel) {
                        Geometry.renderBox(b2RectangleFixtureDefModel.class.cast(obj), position, angle, !bodySelectMode && b2 == data || b2 == secondData);
                    }
                }
                if (null != b2.body) {
                    render.begin(ShapeType.Filled);
                    render.setColor(Color.RED);
                    render.circle(b2.position.x, b2.position.y, 5);
                    render.end();
                }

            }
        } catch (Exception ex) {
            Gdx.app.error("Exception", ex.getMessage());
        }
        // now we draw the joints
        for (b2JointDefModel b2Joint : model.jointDefModels) {
            Geometry.renderJoint(b2Joint);
        }
        if (!bodySelectMode && Gdx.input.isButtonPressed(Buttons.LEFT)) {
            render.begin(ShapeType.Line);
            render.line(startPoint.x, startPoint.y, dragTemp.x, dragTemp.y);
            render.end();
        }
        Gdx.gl.glDisable(GL20.GL_BLEND);

        this.act(delta);
        this.draw();
    }


    @Override
    public InputProcessor getInputProcessor() {
        input.clear();
        input.addProcessor(this);
        input.addProcessor(bodySelectInput);
        return input;
    }

    @Override
    public Class<SceneModelAdapter> getType() {
        return SceneModelAdapter.class;
    }

    public void setData(b2BodyDefModel data) {
        this.data = data;
    }


    /**
     * The menu for make new joints
     */
    class MakeJointsMenu extends Group {
        public MakeJointsMenu() {
            TextureAtlas atlas = Engine.resource("TA");
            Table menuTable = new Table();

            menuTable.pad(20);
            menuTable.setBackground(new NinePatchDrawable(atlas.createPatch("dialog")));

            this.addActor(menuTable);


            addNewHandle(menuTable, "b2DistanceJointDefModel", "DistanceJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    DistanceJointDefModel def = new DistanceJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.localAnchorA.set(def.bodyA.body.getLocalPoint(startPoint.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    def.localAnchorB.set(def.bodyB.body.getLocalPoint(dragTemp.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    def.length = startPoint.dst(dragTemp);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2PrismaticJointDefModel", "PrismaticJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    PrismaticJointDefModel def = new PrismaticJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.anchor.set(startPoint).add(dragTemp).scl(1f / 2);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2RevoluteJointDefModel", "RevoluteJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    RevoluteJointDefModel def = new RevoluteJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.anchor.set(startPoint).add(dragTemp).scl(1f / 2);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2RopeJointDefModel", "RopeJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    RopeJointDefModel def = new RopeJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.localAnchorA.set(def.bodyA.body.getLocalPoint(startPoint.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    def.localAnchorB.set(def.bodyB.body.getLocalPoint(dragTemp.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2WeldJointDefModel", "WeldJointDefModel", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    WeldJointDefModel def = new WeldJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.anchor.set(startPoint).add(dragTemp).scl(1f / 2);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2WheelJointDefModel", "WheelJointDefModel", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    WheelJointDefModel def = new WheelJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.anchor.set(startPoint).add(dragTemp).scl(1f / 2);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2FrictionJointDefModel", "FrictionJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    FrictionJointDefModel def = new FrictionJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.anchor.set(startPoint).add(dragTemp).scl(1f / 2);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            addNewHandle(menuTable, "b2PulleyJointDefModel", "PulleyJoint", new ClickListener() {
                @Override
                public void clicked(InputEvent event, float x, float y) {
                    PulleyJointDefModel def = new PulleyJointDefModel();
                    def.bodyA = data;
                    def.bodyB = secondData;
                    def.localAnchorA.set(def.bodyA.body.getLocalPoint(startPoint.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    def.localAnchorB.set(def.bodyB.body.getLocalPoint(dragTemp.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
                    def.groundAnchorA.set(startPoint.x, 500);
                    def.groundAnchorB.set(dragTemp.x, 500);
                    def.lengthA = startPoint.dst(def.groundAnchorA);
                    def.lengthB = dragTemp.dst(def.groundAnchorB);
                    Geometry.ajustJoint(def);
                    PhysicalWorld.MODEL.addJoint(def);
                    adapter.callUI.addModelToLeft(def);
                    remove();
                }
            });
            menuTable.pack();
        }


        void addNewHandle(Table table, String region, String txt, ClickListener l) {
            TextureAtlas atlas = Engine.resource("TA");
            DefaultLabel label = DefaultLabel.getDefaultLabel(txt + "   ");
            label.addListener(l);
            table.add(new Image(new TextureRegionDrawable(atlas.findRegion(region)), Scaling.none)).height(30).spaceRight(10);
            table.add(label).right().spaceRight(10);
            table.row();
        }
    }
}
